Web components

revision:


Content

intro concepts and usage custom elements are HTML elements, like <div>, <section>, or<article> the shadow DOM is an encapsulated version of the DOM HTML templates Lifecycle hooks the basic approach for implementing a web componentlooks like this: creating a custom element from scratch creating HTML templates - examples Web Components - examples Web Components - more complex examples


intro

top

A Web Component is a way to create an encapsulated, single-responsibility code block, which can be reused on any page. It is a suite of different technologies allowing you to create reusable custom elements, with their functionality encapsulated away from the rest of your code, and utilize them in your web apps.

Using a web component is much like using any other existing HTML element: they can be configured using attributes, queried for using JavaScript, and even styled through CSS. As long as the browser knows they exist, they are treated no differently.


concepts and usage

top

Web Components consists of three main technologies. Each of the technologies can be used independently or combined with any of the others.

Custom elements: a set of JavaScript APIs that allow you to define custom elements and their behavior.

Custom elements are HTML elements (like <div>, <section>or <article>). We can name them ourselves and they are defined via a browser API.
Custom elements are just like standard HTML elements (i.e. names in angle brackets), except that they always have a dash in them, like <news-slider> or <bacon-cheeseburger>. Browser vendors have committed not to create new built-in elements containing a dash in their names to prevent conflicts.
Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.

Shadow DOM: a set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality.

In this way, you can keep an element's features private, so they can be scripted and styled without the fear of collision with other parts of the document.
The shadow DOM is an encapsulated version of the DOM. This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them.
Generally, any content inside of the document's scope is referred to as the light DOM, and anything inside a shadow root is referred to as the shadow DOM.
The shadow DOM works sort of like an <iframe>where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.

HTML templates: the ><template> element and <slot> element enable you to write markup templates that are not displayed in the rendered page.

These can then be reused multiple times as the basis of a custom element's structure. The HTML <template> element allows us to stamp out re-usable templates of code inside a normal HTML flow that won't be immediately rendered, but can be used at a later time.


Custom elements are HTML elements, like <div>, <section> or <article>

top

They are something we can name ourselves and are defined via a browser API.

Custom elements are just like the standard HTML elements — names in angle brackets — except they always have a dash in them, like <news-slider> or <bacon-cheeseburger>.

Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.

There are two kinds of custom elements:

autonomous custom elements : “all-new” elements, extending the abstract HTMLElement class.
customized built-in elements : extending built-in elements, like a customized button, based on HTMLButtonElement etc.

examples

code:
                    <div>
                        <my-component></my-component>
                    </div>
                    <script>
                        class MyComponent extends HTMLElement {
                            // connect component
                            connectedCallback() {
                                this.innerHTML = `<h1>Hello world</h1>`;
                            }
                        }
                        // register component
                        customElements.define('my-component', MyComponent);
                    </script>
                

explanation:

In this example, we define <my-component>, our very own HTML element. It doesn't do much, however this is the basic building block of a custom element.
All custom elements must in some way extend an HTMLElement in order to be registered with the browser.

To do anything useful, the class requires a method named connectedCallback() which is invoked when the element is added to a document.

The class must be registered with the CustomElementRegistry to define it as a handler for a specific element.

The browser now associates the <hello-world> element with the MyComponent class when your JavaScript is loaded (e.g. <script type="module" src="./mycomponent.js"></script>).

Custom elements exist without third-party frameworks and the browser vendors are dedicated to the continued backward compatibility of the spec, all but guaranteeing that components written according to the specifications will not suffer from breaking API changes.

Each custom element has a similar structure.

They extend an existing HTMLElement class, which provides the groundwork for how an element should behave.
Inside, there are a few methods called "reactions" that are called in response to something about that element changing. For example, "connectedCallback" will be called when the new element appears on screen.
These work similarly to the lifecycle methods found in most JavaScript frameworks. Updating the attributes on an element can change how it behaves. When an update happens, the "attributeChangedCallback" reaction will fire, which drives the change. This will only happen for an attribute that is defined inside the observedAttributes array.
An element needs to be defined before the browser can do anything with it. The "define method" here takes two arguments – the tag name, and the class it should use. All tag names must contain a "-"" character to avoid any clashes with any future native elements.

The element can then be written anywhere in the page as a regular HTML tag.

Once a browser has an element defined, it then finds any of these matching tags and links up their behaviour to the class in a process known as 'upgrading'.
There are two types of custom element – 'autonomous' or 'customised built-in'.
Autonomous custom elements are not related to any existing element. Much like a <div>or <span>they do not provide any meaning to their content.
A customised built-in element – as the name implies – can enhance an existing element with new functionality. They maintain that element's normal semantic behaviours, while also being open to change. If an <input>element was customised, for example, it would still be picked up and submitted as part of a form.

The class of customised built-in component extends the class of the element it is customising.

The definition also needs to define the tag of that element through its third argument. They are also used slightly differently.
Instead of a new tag, they extend the existing tag by using the "is" attribute. The browser can read this, and upgrade them in the same way as it can an autonomous component.

CustomElementRegistry: contains functionality related to custom elements, most notably the CustomElementRegistry.define() method used to register new custom elements so they can then be used in your document.

Window.customElements: returns a reference to the CustomElementRegistry object.

Life cycle callbacks: special callback functions defined inside the custom element's class definition, which affect its behavior:
connectedCallback: invoked when the custom element is first connected to the document's DOM.
disconnectedCallback: invoked when the custom element is disconnected from the document's DOM.
adoptedCallback: invoked when the custom element is moved to a new document.
attributeChangedCallback: invoked when one of the custom element's attributes is added, removed, or changed.

Extensions for creating custom built-in elements: The "is" global HTML attribute allows you to specify that a standard HTML element should behave like a registered custom built-in element. The "is" option of the Document.createElement() method allows you to create an instance of a standard HTML element that behaves like a given registered custom built-in element.

CSS pseudo-classes: pseudo-classes relating specifically to custom elements:
:defined: matches any element that is defined, including built in elements and custom elements defined with CustomElementRegistry.define().
:host: selects the shadow host of the shadow DOM containing the CSS it is used inside.
:host(): selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function's parameter matches the shadow host.
:host-context(): selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function's parameter matches the shadow host's ancestor(s) in the place it sits inside the DOM hierarchy.

CSS pseudo-elements: pseudo-elements relating specifically to custom elements:
::part: represents any element within a shadow tree that has a matching part attribute.


The shadow DOM is an encapsulated version of the DOM.

top

The shadow DOM is capable of isolating CSS and JavaScript

Selectors and styles inside of a shadow DOM node don't leak outside of the shadow root and styles from outside the shadow root don't leak in. There are a few exceptions that inherit from the parent document, like font family and document font sizes (e.g. rem) that can be overridden internally.

All shadow roots still exist in the same document so that all code can be written inside a given context but no worry about conflicts with other styles or selectors.

This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them.
Any content inside of the document's scope is referred to as the "light DOM", and anything inside a shadow root is referred to as the "shadow DOM".

When using the light DOM, an element can be selected by using document.querySelector('selector') or by targeting any element's children by using element.querySelector('selector').

In the same way, a shadow root's children can be targeted by calling shadowRoot.querySelector where shadowRoot is a reference to the document fragment — the difference being that the shadow root's children will not be select-able from the light DOM.

For example, If we have a shadow root with a <button> inside of it, calling shadowRoot.querySelector('button') would return our button, but no invocation of the document's query selector will return that element because it belongs to a different DocumentOrShadowRoot instance. Style selectors work in the same way.

In this respect, the shadow DOM works sort of like an <iframe> where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.

A shadow DOM is created when applied to an element.

Any content can be added to the shadow DOM just like the regular – or 'light' – DOM, but it has no effect on what's happening outside of it.
Likewise, nothing in the light DOM can access the shadow DOM directly. This means we can add classes, styles and scripts anywhere in the shadow DOM without worrying about clashes.
The best use of the shadow DOM with web components comes when coupled with a custom element. By having a shadow DOM in charge of the content, any time this component is reused, its styles and structure will not affect the rest of the page.

ShadowRoot: represents the root node of a shadow DOM subtree.

DocumentOrShadowRoot: a mixin defining features that are available across document and shadow roots.

Element extensions: extensions to the Element interface related to shadow DOM: the Element.attachShadow() method attaches a shadow DOM tree to the specified element; the Element.shadowRoot property returns the shadow root attached to the specified element, or null if there is no shadow root attached.

Relevant Node additions: additions to the node interface relevant to shadow DOM: the Node.getRootNode() method returns the context object's root, which optionally includes the shadow root if it is available; the Node.isConnected property returns a boolean indicating whether or not the node is connected (directly or indirectly) to the context object, e.g. the Document object in the case of the normal DOM, or the ShadowRoot in the case of a shadow DOM

Event extensions: Extensions to the event interface related to shadow DOM: Event.composed returns a Boolean which indicates whether the event will propagate across the shadow DOM boundary into the standard DOM (true), or not (false); Event.composedPath returns the event’s path (objects on which listeners will be invoked). This does not include nodes in shadow trees if the shadow root was created with ShadowRoot.mode closed.

examples

This will use the CSS background
code:
                    <div>
                        <div id="example">This will use the CSS background</div>
                        <button id="button">Not tomato</button>
                    </div>
                    <script>
                            const shadowRoot = document.getElementById('example').attachShadow({ mode: 'open' });
                            shadowRoot.innerHTML = `<style>
                            button {
                                background: tomato;
                                color: white;
                            }
                            </style>
                            <button id="button"><slot></slot> tomato</button>`;
                    </script>
                

A shadow root can also include content from its containing document by using the <slot> element. Using a slot will drop user content from the outer document at a designated spot in your shadow root.

The shadow DOM attaches a separated DOM to the Web Component with elem.attachShadow({mode: ...}):

            const shadow = this.attachShadow({ mode: 'closed' });
        

The mode can be:

"open" : JavaScript in the outer page can access the shadow DOM (using Element.shadowRoot);
"closed" : the shadow DOM can only be accessed within the Web Component.

The shadow DOM can be manipulated like any other DOM element:

            connectedCallback() {
                const shadow = this.attachShadow({ mode: 'closed' });
                 shadow.innerHTML = `
                  <style>
                    p {
                      text-align: center;
                      font-weight: normal;
                      padding: 1em;
                      margin: 0 0 2em 0;
                      background-color: #eee;
                      border: 1px solid #666;
                    }
                  </style>
                <p>Hello ${ this.name }!</p>`;
            }
        

explanation:

The component renders the “Hello” text inside a <p> element and styles it. It cannot be modified by JavaScript or CSS outside the component, although some styles such as the font and color are inherited from the page because they were not explicitly defined. The styles scoped to this Web Component cannot affect other paragraphs on the page or even other <hello-world> components.

Note that the CSS :host selector can style the outer <hello-world> element from within the Web Component.


HTML templates

top

The HTML templates specification defines the <template>tag, which can contain anything likely to be reused.

On its own, it has no appearance and remains inert, meaning nothing inside is parsed or executed until told to, including requests for external media such as images or video. JavaScript cannot query the contents either, as browsers will only see it as an empty element.

A regular query will pick up the <template>element itself. The importNode method creates a copy of its contents, with the second argument telling it to take a deep copy of everything. Finally, it can be added to the document like any other element.
Templates can contain anything an HTML page can, including CSS and JavaScript. As soon as the element is applied to the page, those styles apply and the scripts execute.
Bear in mind that these will run globally and so can override styles and values if care isn't taken. The best part about templates is that they are not just limited to web components.
The examples here apply to any web page, but become particularly powerful when paired with web components, in particular the shadow DOM.

<template >: contains an HTML fragment that is not rendered when a containing document is initially loaded, but can be displayed at runtime using JavaScript, mainly used as the basis of custom element structures. The associated DOM interface is HTMLTemplateElement.

<slot >: a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together. The associated DOM interface is HTMLSlotElement.

The slot global HTML attribute: assigns a slot in a shadow DOM shadow tree to an element.

Slotable: a mixin implemented by both Element and Text nodes, defining features that allow them to become the contents of an <slot>element. The mixin defines one attribute, "Slotable.assignedSlot", which returns a reference to the slot the node is inserted in.

Element extensions: extensions to the Element interface related to slots: Element.slot returns the name of the shadow DOM slot attached to the element.

CSS pseudo-elements:Pseudo-elements relating specifically to slots: ::slotted matches any content that is inserted into a slot.

The slotchange event: fired on an HTMLSlotElement instance (<slot>element) when the node(s) contained in that slot changes.

HTML <template> allows to stamp out re-usabe templates of code inside a normal HTML flow, which won't be immediately rendered, but can be used at a later time.

A built-in <template> element serves as a storage for HTML markup templates. The browser ignores its contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.

Its content can be any valid HTML, even if it normally requires a proper enclosing tag. We can put styles and scripts into <template> as well.

The content becomes live (styles apply, scripts run etc) when we insert it into the document.

examples

Choose template
code:
                    <div>
                        <template class="spec-1" id="book-template">
                            <li><span class="title"></span> — <span class="author"></span></li>
                        </template>
                        <template class="spec-1"id="book-template-2">
                            <li><span class="author"></span>'s classic novel <span class="title"></span></li>
                        </template>
                        <ul class="spec-1" id="books"></ul>
                        <fieldset class="spec-1" id="templates">
                            <legend>Choose template</legend>
                            <label>
                                <input type="radio" name="template" value="book-template" checked> Template One
                            </label>
                            <label>
                                <input type="radio" name="template" value="book-template-2"> Template Two
                            </label>
                        </fieldset>
                    </div>
                    <style>
                        label {display: block; margin-bottom: 0.5vw;}
                    </style>
                    <script>
                        'use strict';
            
                        const books = [
                        { title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
                        { title: 'A Farewell to Arms', author: 'Ernest Hemingway' },
                        { title: 'Catch 22', author: 'Joseph Heller' }
                        ];
            
                        function appendBooks(templateId) {
                        const booksList = document.getElementById('books');
                        const fragment = document.getElementById(templateId);
                        // Clear out the content from the ul
                        booksList.innerHTML = '';
                        // Loop over the books and modify the given template
                        books.forEach(book => {
                            // Create an instance of the template content
                            const instance = document.importNode(fragment.content, true);
                            // Add relevant content to the template
                            instance.querySelector('.title').innerHTML = book.title;
                            instance.querySelector('.author').innerHTML = book.author;
                            // Append the instance ot the DOM
                            booksList.appendChild(instance);
                        });  
                        }
                        document.getElementById('templates').addEventListener('change', (event) => appendBooks(event.target.value));
                        appendBooks('book-template');
                    </script>
                

explanation:

The example above wouldn't render any content until a script has consumed the template, instantiated the code and told the browser what to do with it.

Crafting reusable HTML templates

The template elements are "user-defined templates in HTML that aren't rendered until called upon." In other words, a template is HTML that the browser ignores until told to do otherwise.

These templates then can be passed around and reused in a lot of interesting ways.

As simple as it might sound, a <template> is an HTML element, so the most basic form of a template with content would be:

            <template>
                <h1>Hello world</h1>
            </template>
        

Running this in a browser would result in an empty screen as the browser doesn't render the template element's contents. This becomes incredibly powerful because it allows us to define content (or a content structure) and save it for later — instead of writing HTML in JavaScript. In order to use the template, we will need JavaScript.

example

code:
                    <div>
                        <template class="spec-1" id="template1">
                            <h4>Hello fabulous blue planet!</h4>
                        </template>
                    </div>
                    <script>
                        const template = document.querySelector('#template1');
                        const node = document.importNode(template.content, true);
                        document.body.appendChild(node);
                    </script>  
                

Lifecycle hooks

top

Web components have their own lifecycle.

The following events happen in a web component’s lifecycle: element is inserted into the DOM; updates when UI event is being triggered; element deleted from the DOM.
A web component has lifecycle hooks, which are callback functions, to capture these lifecycle events and let us handle them accordingly.
They let us handle these events without creating our own system to do so.
Most JavaScript frameworks provide the same functionality, but web components is a standard so we don’t need to load extra code to be able to use them.

The following lifecycle hooks are in a web component:

The constructor() is called when the web component is created. It’s called when we create the shadow DOM and it’s used for setting up listeners and initialize a component’s state. However, it’s not recommended that we run things like rendering and fetching resources here. The connectedCallback is better for these kinds of tasks. Defining a constructor is optional for ES6 classes, but an empty one will be created when it’s undefined.
When creating the constructor, we’ve to call "super()"" to call the class that the web component class extends. We can have "return" statements in there and we can’t use "document.write()" or "document.open()" in there. Also, we can’t gain attributes or children in the constructor method.

The static get observedAttributes : returns an array of attributes that the browser will monitor for changes.

The connectedCallback() method is called when an element is added to the DOM. We can be sure that the element is available to the DOM when this method is called. This means that we can safely set attributes, fetch resources, run set up code or render templates.

disconnectedCallback() is called when the element is removed from the DOM. Therefore, it’s an ideal place to add cleanup logic and to free up resources. We can also use this callback to: 1/ notify another part of an application that the element is removed from the DOM; 2/ free resources that won’t be garbage collected automatically like unsubscribing from DOM events, stop interval timers, or unregister all registered callbacks. This hook is never called when the user closes the tab and it can be triggered more than once during its lifetime.

attributeChangedCallback(attrName, oldVal, newVal): we can pass attributes with values to a Web Component like any other attribute. In this callback, we can get the value of the attributes as they’re assigned in the code. We can add a "static get observedAttributes()" hook to define what attribute values we observe.

The adoptedCallback() is called when we call "document.adoptNode" with the element passed in. It only occurs when we deal with iframes. The "adoptNode" method is used to transfer a node from one document to another. An iframe has another document, so it’s possible to call this with iframe’s document object


The basic approach for implementing a web component looks like this:

top

1/ Create a class in which you specify your web component functionality, using the ECMAScript 2015 class syntax.

2/ Register your new custom element using the CustomElementRegistry.define() method, passing it the element name to be defined, the class or function in which its functionality is specified, and optionally, what element it inherits from.

3/ If required, attach a shadow DOM to the custom element using Element.attachShadow() method. Add child elements, event listeners, etc., to the shadow DOM using regular DOM methods.

4/ If required, define an HTML template using <template>and <slot >. Use regular DOM methods to clone the template and attach it to your shadow DOM.

5/ Use your custom element wherever you like on your page, just like you would any regular HTML element.


Creating a custom element from scratch

top

The customElements API

This gives us a path to define custom HTML tags that can be used in any document that contains the defining class.

Essentially, a custom element consists of two pieces: a tag name and a class that extends the built-in HTMLElement class. The most basic version of a custom element would look like this:

            class HelloWorld extends HTMLElement {
                // connect component
                connectedCallback() {
                  this.textContent = "Hello, World!";
                }
            }
            customElements.define('hello-world', HelloWorld);
        

explanation:

In the example above, we defined a new standards-compliant HTML element, <hello-world></hello-world>. It doesn't do much… yet. For now, using the <hello-world> tag in any HTML document will create a new element with a textContent reading “Hello, World!”.

Adding attributes

Like any other element, we can add HTML attributes:

<hello-world name="Ann"></hello-world>

This could override the text so "Hello Ann!" is displayed. To achieve this, you can add a constructor() function to the HelloWorld class, which is run when each object is created.
It must: 1/call the super() method to initialize the parent HTMLElement, and 2/ make other initializations. In this case, we'll define a name property that is set to a default of “World”:

            class HelloWorld extends HTMLElement {
                constructor() {
                super();
                this.name = 'World';
                }
   
                // more code...
            }
        

A static observedAttributes() property should return an array of properties to observe:

            // component attributes
            static get observedAttributes() {
              return ['name'];
            }
        

An attributeChangedCallback() method is called when an attribute is defined in the HTML or changed using JavaScript. It's passed the property name, old value, and new value:

            // attribute change
            attributeChangedCallback(property, oldValue, newValue) {
                if (oldValue === newValue) return;
                this[ property ] = newValue;
            }
        

Finally, you need to tweak the message in the connectedCallback() method:

            // connect component
            connectedCallback() {
                this.textContent = `Hello ${ this.name }!`;
            }
        

To create a custom element

1/ use the "customElements.define()" browser API methd and a JS class that extends the HTMLElement:

ex.

class DropDownd extends HTMLElement{
                // define behavior here
    }
    window.customElements.define('drop-down', DropDown);
    

You can also use an anonymous class:

ex.

    window.customElements.define{'drop-down', class extends HTMLElement{
                        // define behavior here

            }}
    

2/ once the custom element is defined, it can be used in a web page:

ex.

 <drop-down ></drop-down >

3/ properties can be defined on a custom element:

ex.

    class DropDown extends HTMLElement{
        // set the "fill" property
        set fill(option){
            this.setAttribute('fill', option);
        }
        // get the "fill" property
        get fill(){
             return this.Attribute('fill');
        }
    }
    

and the usage in the browser:

    <drop-down fill="true"><</drop-down>
    

4/ you can also define a constructor in the class:

ex.

    class DropDown extends HTMLElement{
        constructor(){
            super();
        }
    }
    

creating HTML templates - examples

top

The versatility of template: one of the interesting things about templates is that they can contain any HTML. That includes script and style elements. A very simple example would be a template that appends a button that alerts us when it is clicked.

example

code:
                <div>       
                    <template class="spec-1" id="template1">
                        <script>                
                            const button = document.getElementById('click-me');
                            button.addEventListener('click', event => alert(event));
                        </script>
                        <style>
                            #click-me {all: unset; background: tomato;  border: 0; border-radius: 4px; color: white; font-family: Helvetica; 
                                font-size: 1vw; padding: .5vw 1vw; margin-inline: 2vw;}
                        </style>
                        <button class="spec-1" id="click-me">Log click event</button>
                    </template>
                    <script>
                        'use strict';
                        const template1 = document.getElementById('template1');
                        document.body.appendChild(document.importNode(template1.content, true));
                    </script>
                </div>
                    
            


code:
                <div>
                    <template class="spec-1" id="dialog-template">
                        <script>
                            document.getElementById('launch-dialog').addEventListener('click', () => {
                            const wrapper = document.querySelector('.wrapper');
                            const closeButton = document.querySelector('button.close');
                            const wasFocused = document.activeElement;
                            wrapper.classList.add('open');
                            closeButton.focus();
                            closeButton.addEventListener('click', () => {
                                wrapper.classList.remove('open');
                                wasFocused.focus();
                            });
                        });
                        </script>
                        <style>
                            .wrapper {opacity: 0; transition: visibility 0s, opacity 0.25s ease-in; }
                            .wrapper:not(.open) {visibility: hidden;}
                            .wrapper.open {align-items: center; display: flex; justify-content: center; height: 100vh;   position: fixed; top: 0; 
                                left: 0; right: 0; bottom: 0;  opacity: 1; visibility: visible;}
                            .overlay {background: rgba(0, 0, 0, 0.8); height: 100%; position: fixed; top: 0; right: 0; bottom: 0; left: 0; 
                                width: 100%; }
                            .dialog { background: #ffffff; max-width: 600px; padding: 1rem; position: fixed; }
                            button {all: unset; cursor: pointer; font-size: 1.25rem; position: absolute; top: 1rem; right: 1rem;}
                            button:focus {border: 2px solid blue;            }
                        </style>
                        <div class="wrapper">
                            <div class="overlay"></div>
                            <div class="dialog" role="dialog" aria-labelledby="title" aria-describedby="content">
                                <button class="close" aria-label="Close">✖️</button>
                                <h1 id="title">Hello world</h1>
                                <div id="content" class="content">
                                <p>This is content in the body of our modal</p>
                                </div>
                            </div>
                        </div>
                    </template>
                    <button class="spec-1"  id="launch-dialog">Launch dialog</button>
                    <style>
                        #launch-dialog {background: tomato; border-radius: 4px; color: #fff;font-family: Helvetica, Arial, sans-serif; 
                            padding: 0.5rem 1rem; position: static;}
                    </style>
                    <script>
                         const dialogTemplate = document.getElementById('dialog-template');
                        document.body.appendChild(document.importNode(dialogTemplate.content, true));
                    </script>
                </div>
            

Web Components - examples

top
code:
                <div>
                    <div id="DIV-A"></div>
                    <template id="my-paragraph"><p id="P-one">My first paragraph</p></template>   
                </div>
                <style>
                    p#P-one { color: lightblue; background-color: #666; padding: 5px;}
                </style>
                <script>
                    let template_A = document.getElementById('my-paragraph');
                    let templateContent = template_A.content;
                    document.getElementById('DIV-A').appendChild(templateContent);
                </script>
            

code:
                <div>
                    <div id="DIV-B"></div>     
                    <template id="paragraph-one">
                        <p id="P-two">
                            <slot name="my-text">My default text</slot>
                        </p>
                    </template>
                </div>
                <style>
                    p#P-two { color: lightgreen; background-color: #666; padding: 5px;}
                </style>
                <script>
                    let template_B = document.getElementById('paragraph-one');
                    let templateContent_B = template_B.content;
                    document.getElementById("DIV-B").appendChild(templateContent_B);
                </script>
            

code:
                <div>
                    <div id="DIV-C"></div>
                    <template id="paragraph-two">
                        <style>
                            p#P-three { color: orange; background-color: #666; padding: 5px;}
                        </style>
                        <p id="P-three"><slot name="text-one">My second default text</slot></p>
                    </template>
                </div>
                <script>
                    let template_C = document.getElementById('paragraph-two');
                    let templateContent_C = template_C.content;
                    document.getElementById("DIV-C").appendChild(templateContent_C);
                </script>
            

Let's have some different text!
code:
               <div>
                    <div id=""DIV-D></div>
                    <my-paragraph id="paragraph-three">
                        <span id="span_1" slot="text-two">Let's have some different text!</span>
                    </my-paragraph>
                </div>
                <style>
                    #span_1 { color: yellow; background-color: #666; padding: 5px;}
                </style>
                <script>
                    customElements.define('paragraph-three',
                        class extends HTMLElement {
                            constructor() {
                                super();
                                const template_D = document.getElementById('paragraph-three');
                                const templateContent_D = template_D.content;
                                this.attachShadow({mode: 'open'}).appendChild(templateContent_D.cloneNode(true));
                            }
                        }
                    ); 
                </script>
            

  • Let's have still a somewhat different text!
  • In a list!
code:
                <div>
                    <my-paragraph id="para-four">
                        <ul id="ul-1" slot="my-text">
                            <li>Let's have still a somewhat different text!</li>
                            <li>In a list!</li>
                        </ul>
                    </my-paragraph>
                </div>
                <style>
                #ul-1 { color: orange; background-color: #666; padding: 5px;}
            </style>
                <script>
                    customElements.define('para-four',
                        class extends HTMLElement {
                            constructor() {
                                super();
                                const template_E = document.getElementById('para-four');
                                const templateContent_E = template_E.content;
                                this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));
                            }
                        }
                    ); 
                </script>
            

code:
                <div>
                    <my-paragraph>
                        <add-text id="text-1"></add-text>
                    </my-paragraph>
                </div>
                <script>
                    class AddText extends HTMLElement {
                        constructor() {
                            super();
                            var host = this.attachShadow({mode: 'open'});
                            const div=document.createElement('div');
                                div.setAttribute('class', 'main');
                            const p=document.createElement('p');
                                p.textContent='Web Components are awesome, what do you think?';
                            const style=document.createElement('style');
                                style.textContent=' p {color:burlywood; background-color: green; padding:0.5vw;} ';
                                host.appendChild(style);
                                host.appendChild(div);
                                div.appendChild(p);
                        }
                    }
                    customElements.define('add-text', AddText);
                    var host = document.querySelector(".main");
                    var root = host.createShadowRoot();
                    root.innerHTML = '<p> <strong>Web Components</strong> are awesome, what do you think?</p>'
                </script>
            

Web Components - more complex examples

top

enter some div

examples


code:
                    <div>
                        <form>
                            <div>
                                <label for="cvc">Enter your CVC <popup-info img="../pics/download.png" data-text=
                                "Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers 
                                on the back of your card."></popup-info></label>
                            <input type="text" id="cvc">
                            </div>
                        </form>
                        <br><br>
                    </div>
                    <script>
                        // Create a class for the element
                        class PopUpInfo extends HTMLElement {
                            constructor() {
                            // Always call super first in constructor
                            super();
                            // Create a shadow root
                            const shadow = this.attachShadow({mode: 'open'});
                            // Create spans
                            const wrapper = document.createElement('span');
                                wrapper.setAttribute('class', 'wrapper');
                            const icon = document.createElement('span');
                                icon.setAttribute('class', 'icon');
                                icon.setAttribute('tabindex', 0);
                            const info = document.createElement('span');
                            info.setAttribute('class', 'info');
                            // Take attribute content and put it inside the info span
                            const text = this.getAttribute('data-text');
                                info.textContent = text;
                            // Insert icon
                            let imgUrl;
                                if(this.hasAttribute('img')) {
                                imgUrl = this.getAttribute('img');
                                } else {
                                imgUrl = 'default.png';
                            }
                            const img = document.createElement('img');
                                img.src = imgUrl;
                                icon.appendChild(img);
                            // Create some CSS to apply to the shadow dom
                            const style = document.createElement('style');
                            console.log(style.isConnected);
                            style.textContent = `
                                    .wrapper {position: relative;}
                                    .info {font-size: 0.8rem;  width: 200px; display: inline-block; border: 1px solid black; 
                                        padding: 10px; background: white; border-radius: 10px; opacity: 0; transition: 0.6s all; 
                                        position: absolute; bottom: 20px; left: 10px;       z-index: 3; }
                                    img {width: 1.2rem;}
                                    .icon:hover + .info, .icon:focus + .info {opacity: 1;}
                            `;
                            // Attach the created elements to the shadow dom
                            shadow.appendChild(style);
                            console.log(style.isConnected);
                            shadow.appendChild(wrapper);
                            wrapper.appendChild(icon);
                            wrapper.appendChild(info);
                        }
                        }
                        // Define the new element
                        customElements.define('popup-info', PopUpInfo);
                    </script>
                

expanding list web component

examples
  • UK
    • Yorkshire
      • Leeds
        • Train station
        • Town hall
        • Headrow
      • Bradford
      • Hull
  • USA
    • California
      • Los Angeles
      • San Francisco
      • Berkeley
    • Nevada
    • Oregon
  • Not
  • an
  • expanding
  • list
code:
                    <div>
                        <ul is="expanding-list">
                            <li>UK
                                <ul>
                                    <li>Yorkshire
                                        <ul>
                                            <li>Leeds
                                                <ul>
                                                    <li>Train station</li>
                                                    <li>Town hall</li>
                                                    <li>Headrow</li>
                                                </ul>
                                            </li>
                                            <li>Bradford</li>
                                            <li>Hull</li>
                                        </ul>
                                    </li>
                                </ul>
                            </li>
                            <li>USA
                                <ul>
                                    <li>California
                                        <ul>
                                            <li>Los Angeles</li>
                                            <li>San Francisco</li>
                                            <li>Berkeley</li>
                                        </ul>
                                    </li>
                                    <li>Nevada</li>
                                    <li>Oregon</li>
                                </ul>
                            </li>
                        </ul>
            
                        <ul>
                            <li>Not</li>
                            <li>an</li>
                            <li>expanding</li>
                            <li>list</li>
                        </ul>
                    </div>
                    <script>
                        // Create a class for the element
                        class ExpandingList extends HTMLUListElement {
                            constructor() {
                            // Always call super first in constructor; return value from super() is a reference to this element
                            self = super();
            
                            // Get ul and li elements that are a child of this custom ul element
                            // li elements can be containers if they have uls within them
                            const uls = Array.from(self.querySelectorAll('ul'));
                            const lis = Array.from(self.querySelectorAll('li'));
            
                            // Hide all child uls
                            // These lists will be shown when the user clicks a higher level container
                            uls.forEach(ul => {
                            ul.style.display = 'none';
                            });
            
                                // Look through each li element in the ul
                            lis.forEach(li => {
                            // If this li has a ul as a child, decorate it and add a click handler
                            if (li.querySelectorAll('ul').length > 0) {
                                // Add an attribute which can be used  by the style
                                // to show an open or closed icon
                                li.setAttribute('class', 'closed');
            
                                // Wrap the li element's text in a new span element
                                // so we can assign style and event handlers to the span
                                const childText = li.childNodes[0];
                                const newSpan = document.createElement('span');
            
                                // Copy text from li to span, set cursor style
                                newSpan.textContent = childText.textContent;
                                newSpan.style.cursor = 'pointer';
                                
                                // Add click handler to this span
                                newSpan.onclick = self.showul;
                                
                                // Add the span and remove the bare text node from the li
                                childText.parentNode.insertBefore(newSpan, childText);
                                childText.parentNode.removeChild(childText);
                            }
                            });
                        }
            
                        // li click handler
                        showul = function (e) {
                            // next sibling to the span should be the ul
                            const nextul = e.target.nextElementSibling;
            
                            // Toggle visible state and update class attribute on ul
                            if (nextul.style.display == 'block') {
                            nextul.style.display = 'none';
                            nextul.parentNode.setAttribute('class', 'closed');
                            } else {
                            nextul.style.display = 'block';
                            nextul.parentNode.setAttribute('class', 'open');
                            }
                        };
                        }
            
                        // Define the new element
                        customElements.define('expanding-list', ExpandingList, { extends: 'ul' });
                    </script>
                

another custom element

examples
code:
                    <div>
                        <custom-element foo="foo" bar="bar" baz="baz"></custom-element>
                    </div>
                    <script>
                        class BlinkElement extends HTMLElement {
                            constructor() {
                                super();
                            }
                
                            connectedCallback() {
                                const shadow = this.attachShadow({mode: 'open'});
                                this.span = document.createElement('span');
                                this.span.textContent = this.getAttribute('text');
                                const style = document.createElement('style');
                                style.textContent = 'span { color: black; background-color: lightblue; margin-left: 2vw; font-size: 2vw; }';
                                this.intervalTimer = setInterval(() => {
                                let styleText = this.style.textContent;
                                if (style.textContent.includes('red')) {
                                    style.textContent = 'span { color: black }';
                                } else {
                                    style.textContent = 'span { color: red }';
                                }
                
                            }, 2000)
                                shadow.appendChild(style);
                                shadow.appendChild(this.span);
                            }
                
                            disconnectedCallback() {
                                clearInterval(this.intervalTimer);
                            }
                
                            static get observedAttributes() {
                                return ['text'];
                            }
                
                            attributeChangedCallback(name, oldValue, newValue) {
                                if (name === 'text') {
                                if (this.span) {
                                    this.span.textContent = newValue;
                                }
                
                                }
                            }
                
                            
                        }
                        const blink2 = document.createElement('blink-element');
                        document.body.appendChild(blink2);
                        blink2.setAttribute('text', 'bar');
                        blink2.setAttribute('text', 'baz');
                
                        // const blink2 = document.createElement('blink-element');
                        // document.body.appendChild(blink2);
                        // blink2.setAttribute('text', 'bar');
                        // document.body.removeChild(blink2);
                
                        customElements.define('blink-element', BlinkElement);
                    </script>